package hr.jpa.dao;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;

import javax.persistence.EntityManager;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.NoResultException;
import javax.persistence.Query;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Example;
import org.jboss.seam.annotations.In;

public class GenericDAOImpl<T, ID extends Serializable> implements
		GenericDAO<T, ID> {

	private Class<T> persistentClass;

	@In
	protected EntityManager entityManager;

	@SuppressWarnings("unchecked")
	public GenericDAOImpl() {
		try {
			this.persistentClass = (Class<T>) ((ParameterizedType) getClass()
					.getGenericSuperclass()).getActualTypeArguments()[0];
		} catch (final Exception e) {
			// throw new RuntimeException(e);
		}
	}

	public GenericDAOImpl(final Class<T> persistentClass,
			final EntityManager entityManager) {
		this.persistentClass = persistentClass;
		this.entityManager = entityManager;
	}

	public Class<T> getPersistentClass() {
		return persistentClass;
	}

	public T find(final ID id, final boolean lock) {
		T entity;
		if (lock) {
			entity = entityManager.find(getPersistentClass(), id);
		} else {
			entity = entityManager.find(getPersistentClass(), id);
		}

		return entity;
	}

	public T find(final ID id) {
		return find(id, false);

	}

	public List<T> findAll() {
		return findByCriteria();
	}

	@SuppressWarnings("unchecked")
	public List<T> findByExample(final T exampleInstance,
			final String[] excludeProperty) {
		final Criteria crit = ((Session) entityManager.getDelegate())
				.createCriteria(getPersistentClass());
		final Example example = Example.create(exampleInstance);
		for (final String exclude : excludeProperty) {
			example.excludeProperty(exclude);
		}
		crit.add(example);
		return crit.list();
	}

	@Override
	public List<T> findByExample(final T exampleInstance) {
		return findByExample(exampleInstance, new String[] {});
	}

	public T merge(final T entity) {
		entityManager.merge(entity);
		return entity;
	}

	public void remove(final T entity) {
		entityManager.remove(entity);
	}

	public void refresh(final T entity) {
		entityManager.refresh(entity);
	}

	public void persist(final T entity) {
		entityManager.persist(entity);
	}

	public void flush() {
		entityManager.flush();
	}

	public void clear() {
		entityManager.clear();
	}

	/**
	 * Use this inside subclasses as a convenience method.
	 */
	@SuppressWarnings("unchecked")
	protected List<T> findByCriteria(final Criterion... criterion) {
		final Criteria crit = ((Session) entityManager.getDelegate())
				.createCriteria(getPersistentClass());
		for (final Criterion c : criterion) {
			crit.add(c);
		}
		return crit.list();
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<T> executeNamedQueryNP(final String queryName,
			final Object... arguments) {
		final Annotation entityAnnotation = getPersistentClass().getAnnotation(
				NamedQueries.class);
		final NamedQueries namedQueries = (NamedQueries) entityAnnotation;
		if (namedQueries == null) {
			throw new RuntimeException("Missing valid query: "
					+ getPersistentClass() + ":" + queryName);
		}
		final NamedQuery[] namedQuery = namedQueries.value();
		if (namedQuery == null || namedQuery.length == 0) {
			throw new RuntimeException("Missing valid query: "
					+ getPersistentClass() + ":" + queryName);
		}

		for (final NamedQuery nq : namedQuery) {
			if (nq.name().compareToIgnoreCase(queryName) == 0) {
				final List<String> queryParameters = new LinkedList<String>();
				final Query query = entityManager.createNamedQuery(queryName);

				final String annotatedQuery = nq.query();
				final StringTokenizer st = new StringTokenizer(annotatedQuery);
				while (st.hasMoreTokens()) {
					final String token = st.nextToken();
					if (token.trim().startsWith(":")) {
						queryParameters.add(token.substring(1));
					}
				}
				if (queryParameters.isEmpty()
						|| queryParameters.size() != arguments.length) {
					throw new RuntimeException("Missing query parameters: "
							+ getPersistentClass() + ":" + queryName);
				}

				int argIndex = 0;
				for (final String parameter : queryParameters) {
					query.setParameter(parameter, arguments[argIndex]);
					argIndex++;
				}

				try {
					return query.getResultList();
				} catch (final NoResultException e) {
					e.printStackTrace();
					return Collections.EMPTY_LIST;
				}
			}
		}
		return null;
	}

	@SuppressWarnings("unchecked")
	public List<T> executeNamedQuery(final String name, final Object... args) {
		final Query query = entityManager.createNamedQuery(name);
		for (int i = 0; i < args.length; i++) {
			query.setParameter(i + 1, args[i]);
		}
		return query.getResultList();
	}

}
